﻿#region Copyright (c) 2015 KEngine / Kelly <http://github.com/mr-kelly>, All rights reserved.

// KEngine - Toolset and framework for Unity3D
// ===================================
// 
// Date:     2015/12/03
// Author:  Kelly
// Email: 23110388@qq.com
// Github: https://github.com/mr-kelly/KEngine
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3.0 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library.

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using KEngine;
using LuaInterface;

namespace KSFramework
{
    public class LuaModule : IModuleInitable
    {
        private readonly LuaState _luaState;
        private LuaLooper loop;
        public static LuaModule Instance = new LuaModule();

        public bool IsInited { get; private set; }

        private double _initProgress = 0;

        public double InitProgress
        {
            get { return _initProgress; }
        }

        public LuaState State
        {
            get { return _luaState; }
        }

        /// <summary>
        /// 是否开启缓存模式，默认true，首次执行将把执行结果table存起来；在非缓存模式下，也可以通过编辑器的Reload来进行强制刷新缓存
        /// 对实时性重载要求高的，可以把开关设置成false，长期都进行Lua脚本重载，理论上会消耗额外的性能用于语法解析
        /// 
        /// 一般的脚本语言，如Python, NodeJS中，其import, require关键字都会对加载过的模块进行缓存(包括Lua原生的require)；如果不缓存，要注意状态的保存问题
        /// 该值调用频繁，就不放ini了
        /// </summary>
        public static bool CacheMode = false;

        /// <summary>
        /// Import result object caching
        /// </summary>
        Dictionary<string, object> _importCache = new Dictionary<string, object>();

        protected LuaModule()
        {
#if UNITY_EDITOR
            UnityEngine.Debug.Log("Consturct LuaModule...");
#endif
            _luaState = new LuaState();
        }

        /// <summary>
        /// 初始化加载第三方库
        /// </summary>
        void OpenLibs()
        {
            _luaState.OpenLibs(LuaDLL.luaopen_pb);
            _luaState.OpenLibs(LuaDLL.luaopen_lpeg);
            _luaState.OpenLibs(LuaDLL.luaopen_bit);
            _luaState.OpenLibs(LuaDLL.luaopen_socket_core);

            OpenCJson();
        }

        protected void OpenCJson()
        {
            _luaState.LuaGetField(LuaIndexes.LUA_REGISTRYINDEX, "_LOADED");
            _luaState.OpenLibs(LuaDLL.luaopen_cjson);
            _luaState.LuaSetField(-2, "cjson");

            _luaState.OpenLibs(LuaDLL.luaopen_cjson_safe);
            _luaState.LuaSetField(-2, "cjson.safe");
        }

        /// <summary>
        /// Call script of script path (relative) specify
        /// 
        /// We don't recommend use this method, please use ImportScript which has Caching!
        /// </summary>
        /// <param name="scriptRelativePath"></param>
        /// <returns></returns>
        public object CallScript(string scriptRelativePath)
        {
//            Debuger.Assert(HasScript(scriptRelativePath), "Not exist Lua: " + scriptRelativePath);
//
//            var scriptPath = GetScriptPath(scriptRelativePath);
//            byte[] script;
//            if (Log.IsUnityEditor)
//                script = File.ReadAllBytes(scriptPath);
//            else
//                script = KResourceModule.LoadSyncFromStreamingAssets(scriptPath);
//            string fileName = scriptRelativePath.Substring(scriptRelativePath.LastIndexOf("/"));
//            return _luaState.LuaLoadBuffer<LuaTable>(script, fileName);
//            _luaState.DoFile(scriptRelativePath);
            return _luaState.DoFile<LuaTable>(scriptRelativePath);
        }

        public object[] CallMethod(string moduleName, string funcName, params object[] args)
        {
            LuaFunction func = _luaState.GetFunction(moduleName + "." + funcName);
            if (func != null)
            {
                return func.LazyCall(args);
            }

            return null;
        }

        /// <summary>
        /// Get script full path
        /// </summary>
        /// <param name="scriptRelativePath"></param>
        /// <returns></returns>
        static string GetScriptPath(string scriptRelativePath)
        {
            var luaPath = AppEngine.GetConfig("KSFramework.Lua", "LuaPath");
            var ext = AppEngine.GetConfig("KEngine", "AssetBundleExt");

            var relativePath = string.Format("{0}/{1}.lua", luaPath, scriptRelativePath);

            if (Log.IsUnityEditor)
            {
                var editorLuaScriptPath = Path.Combine(KResourceModule.EditorProductFullPath,
                    relativePath);

                return editorLuaScriptPath;
            }

            relativePath += ext;
            return relativePath;
        }

        /// <summary>
        /// whether the script file exists?
        /// </summary>
        /// <param name="scriptRelativePath"></param>
        /// <returns></returns>
        public bool HasScript(string scriptRelativePath)
        {
            var scriptPath = GetScriptPath(scriptRelativePath);
            if (Log.IsUnityEditor)
                return File.Exists(scriptPath);
            else
                return KResourceModule.IsStreamingAssetsExists(scriptPath);
        }

        /// <summary>
        /// Import script, with caching
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public object Import(string fileName)
        {
            if (!HasScript(fileName))
                throw new FileNotFoundException(string.Format("Not found UI Lua Script: {0}", fileName));

            return DoImportScript(fileName);
        }

        /// <summary>
        /// Try import script, if 
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public bool TryImport(string fileName, out object result)
        {
            result = null;

            if (!HasScript(fileName))
                return false;

            result = DoImportScript(fileName);
            return true;
        }

        object DoImportScript(string fileName)
        {
            object obj;
            if (!_importCache.TryGetValue(fileName, out obj))
            {
                obj = this.CallScript(fileName);
                if (CacheMode)
                    _importCache[fileName] = obj;
            }

            return obj;
        }


        /// <summary>
        /// Clear all imported cache
        /// </summary>
        public void ClearAllCache()
        {
            _importCache.Clear();
        }

        /// <summary>
        /// Clear dest lua script cache
        /// </summary>
        /// <param name="uiLuaPath"></param>
        /// <returns></returns>
        public bool ClearCache(string uiLuaPath)
        {
            return _importCache.Remove(uiLuaPath);
        }

        public IEnumerator Init()
        {
            OpenLibs();
            int frameCount = 0;
            _luaState.LuaSetTop(0);
            LuaBinder.Bind(_luaState);
            DelegateFactory.Init();
            LuaCoroutine.Register(_luaState, AppEngine.EngineInstance);

            _luaState.Start(); //启动LUAVM
            StartLooper();
            IsInited = true;
            yield return null;
        }

        void StartLooper()
        {
            loop = AppEngine.EngineInstance.gameObject.AddComponent<LuaLooper>();
            loop.luaState = _luaState;
        }
    }
}